Skip to content

OCPBUGS-100298: pin internal-lb-monitor pollers to worker nodes - #31466

Open
mkowalski wants to merge 1 commit into
openshift:mainfrom
mkowalski:ocpbugs-100298-pin-internal-lb-monitor-to-workers
Open

OCPBUGS-100298: pin internal-lb-monitor pollers to worker nodes#31466
mkowalski wants to merge 1 commit into
openshift:mainfrom
mkowalski:ocpbugs-100298-pin-internal-lb-monitor-to-workers

Conversation

@mkowalski

@mkowalski mkowalski commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Fixes the internal-LB new-connections "disruption regression" tracked in OCPBUGS-100298.

What was happening

The internal-lb-monitor poller deployment tolerates the control-plane NoSchedule taint but has no nodeSelector. Its replicas normally land on workers, but when a replica is evicted mid-run — the [sig-node] NoExecuteTaintManager Multiple Pods [Serial] test taints the worker hosting it — the replacement is scheduled ~1s later while the taint is still active, and if no untainted worker is free (anti-affinity holds the others), it lands on a control-plane node.

On GCP that vantage point measures a platform hairpin gap instead of user-perceived availability. During every kube-apiserver graceful shutdown on that node:

  1. /readyz goes red at T+1s
  2. apiserver-watcher writes the VIP downfile at T+15s (8 × 2s failures) → gcp-routes removes the local VIP DNAT
  3. GCP's passthrough-ILB health check only deprograms the backend at T+25–30s (CheckIntervalSec: 5 × UnhealthyThreshold: 6)
  4. In the ~10s gap, the node's own new connections to the api-int VIP are blackholed (a backend VM cannot reach its own VIP through the fabric); reused connections survive via conntrack

Result: 2–12 disruption events per internal-lb new-connections backend per run, in two bursts (the hosting master rolls twice during the ensures external ip policy test). Verified across 7 runs of periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial: disruption occurs iff the replacement replica landed on a master (audit-log source IPs, scheduling events, gcp-routes journals and apiserver-watcher logs all line up; full evidence in the Jira). The previously suspected cluster-kube-apiserver-operator#2247 and openshift/kubernetes#2713 are exonerated — the payload correlation was scheduler-placement luck.

The fix

Pin the internal-lb pollers to worker nodes with a nodeSelector. The master toleration is kept so the pods remain schedulable on compact/SNO clusters where control-plane nodes also carry the worker role.


This PR was generated using AI (Assisted-By: Claude Fable 5). Please verify before acting on it.

Summary by CodeRabbit

  • Bug Fixes
    • Improved internal load-balancer polling reliability by scheduling pollers on worker nodes.
    • Preserved compatibility with compact clusters by allowing scheduling on control-plane nodes when necessary.

The internal-lb disruption poller tolerates the control-plane NoSchedule
taint but has no nodeSelector, so when a replica is evicted mid-run (for
example by the NoExecuteTaintManager serial tests tainting the worker it
runs on) its replacement can be scheduled onto a control-plane node.

On GCP that vantage point produces false internal-lb new-connections
disruption on every kube-apiserver rollout of that node: apiserver-watcher
removes the local VIP redirect ~15s into the graceful shutdown while the
GCP passthrough LB health check only deprograms the backend ~25-30s in,
and during the gap the node's own new connections to the api-int VIP are
blackholed (backend VMs cannot reach their own VIP through the fabric).
Reused connections survive via conntrack, so only new-connections
backends are affected, matching the regression reported in
OCPBUGS-100298.

Pin the pollers to worker nodes so the measurement reflects the client
path users actually take. The master toleration is kept so the pods stay
schedulable on compact and single-node clusters, where control-plane
nodes also carry the worker role.

Assisted-By: Claude Fable 5
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: automatic mode

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 31, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@mkowalski: This pull request references Jira Issue OCPBUGS-100298, which is invalid:

  • expected the bug to target the "5.0.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Fixes the internal-LB new-connections "disruption regression" tracked in OCPBUGS-100298.

What was happening

The internal-lb-monitor poller deployment tolerates the control-plane NoSchedule taint but has no nodeSelector. Its replicas normally land on workers, but when a replica is evicted mid-run — the [sig-node] NoExecuteTaintManager Multiple Pods [Serial] test taints the worker hosting it — the replacement is scheduled ~1s later while the taint is still active, and if no untainted worker is free (anti-affinity holds the others), it lands on a control-plane node.

On GCP that vantage point measures a platform hairpin gap instead of user-perceived availability. During every kube-apiserver graceful shutdown on that node:

  1. /readyz goes red at T+1s
  2. apiserver-watcher writes the VIP downfile at T+15s (8 × 2s failures) → gcp-routes removes the local VIP DNAT
  3. GCP's passthrough-ILB health check only deprograms the backend at T+25–30s (CheckIntervalSec: 5 × UnhealthyThreshold: 6)
  4. In the ~10s gap, the node's own new connections to the api-int VIP are blackholed (a backend VM cannot reach its own VIP through the fabric); reused connections survive via conntrack

Result: 2–12 disruption events per internal-lb new-connections backend per run, in two bursts (the hosting master rolls twice during the ensures external ip policy test). Verified across 7 runs of periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial: disruption occurs iff the replacement replica landed on a master (audit-log source IPs, scheduling events, gcp-routes journals and apiserver-watcher logs all line up; full evidence in the Jira). The previously suspected cluster-kube-apiserver-operator#2247 and openshift/kubernetes#2713 are exonerated — the payload correlation was scheduler-placement luck.

The fix

Pin the internal-lb pollers to worker nodes with a nodeSelector. The master toleration is kept so the pods remain schedulable on compact/SNO clusters where control-plane nodes also carry the worker role.


This PR was generated using AI (Assisted-By: Claude Fable 5). Please verify before acting on it.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot openshift-ci-robot added the jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. label Jul 31, 2026
@openshift-ci

openshift-ci Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mkowalski

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 31, 2026
@openshift-ci
openshift-ci Bot requested review from deads2k and sjenning July 31, 2026 15:18
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Walkthrough

The deployment manifest now schedules internal load-balancer pollers on worker nodes. It retains master-node toleration for compact clusters and documents the control-plane hairpin behavior.

Changes

Internal load-balancer placement

Layer / File(s) Summary
Worker-node selection
pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/dep-internal-lb.yaml
The deployment adds a worker-node nodeSelector and documents placement and disruption-measurement behavior.

Estimated code review effort: 1 (Trivial) | ~3 minutes


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Container-Privileges ❌ Error The deployment manifest contains privileged: true, runAsUser: 0, and hostNetwork: true; these match multiple explicitly flagged privilege settings. Remove privileged and hostNetwork access and avoid root where possible; if the disruption test requires them, document the necessity and apply the narrowest security context.
Topology-Aware Scheduling Compatibility ⚠️ Warning The manifest adds a hard worker nodeSelector, but the monitor has no topology check or fallback; TNF has no dedicated workers, so control-plane-only nodes can leave the pod Pending. Use topology-aware placement that accepts dual-role control-plane/worker nodes, or detect SNO/TNF and avoid a worker-only selector; verify TNA and HyperShift placement.
✅ Passed checks (13 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the bug and the main change: pinning internal load-balancer monitor pollers to worker nodes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed The PR changes only a YAML Deployment manifest; it adds a nodeSelector and comments, with no Ginkgo test titles or dynamic test names.
Test Structure And Quality ✅ Passed The commit changes only a Deployment YAML manifest; it adds no Ginkgo test code, It blocks, resource setup, waits, or assertions to review.
Microshift Test Compatibility ✅ Passed The pull request changes only a Deployment YAML manifest; it adds no Ginkgo tests or test references to MicroShift-incompatible APIs or features.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The pull request changes only a YAML Deployment manifest; it adds no Ginkgo tests, so the SNO test-compatibility check is not applicable.
Ote Binary Stdout Contract ✅ Passed PR modifies only a Kubernetes YAML deployment manifest, not Go test binary code. The OTE Binary Stdout Contract check applies only to Go process-level and suite setup code; it does not apply to man...
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The commit changes only a Deployment manifest; it adds no Ginkgo tests or new external connectivity and introduces no flagged IPv4-only test assumption.
No-Weak-Crypto ✅ Passed The PR modifies only a Kubernetes deployment manifest file with configuration changes and comments. No cryptographic implementations, weak crypto usage, or non-constant-time secret comparisons are...
No-Sensitive-Data-In-Logs ✅ Passed The PR changes only a nodeSelector and explanatory comments; added lines contain no logging constructs, credentials, tokens, PII, customer data, or concrete internal hostnames.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@mkowalski

Copy link
Copy Markdown
Contributor Author

/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial

1 similar comment
@mkowalski

Copy link
Copy Markdown
Contributor Author

/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
/payload-job periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial

@openshift-ci

openshift-ci Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@mkowalski: trigger 5 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/53806120-8cf3-11f1-8f6c-5cf00301b374-0

@openshift-ci-robot openshift-ci-robot added the jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. label Jul 31, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@mkowalski: This pull request references Jira Issue OCPBUGS-100298, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state New, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

Fixes the internal-LB new-connections "disruption regression" tracked in OCPBUGS-100298.

What was happening

The internal-lb-monitor poller deployment tolerates the control-plane NoSchedule taint but has no nodeSelector. Its replicas normally land on workers, but when a replica is evicted mid-run — the [sig-node] NoExecuteTaintManager Multiple Pods [Serial] test taints the worker hosting it — the replacement is scheduled ~1s later while the taint is still active, and if no untainted worker is free (anti-affinity holds the others), it lands on a control-plane node.

On GCP that vantage point measures a platform hairpin gap instead of user-perceived availability. During every kube-apiserver graceful shutdown on that node:

  1. /readyz goes red at T+1s
  2. apiserver-watcher writes the VIP downfile at T+15s (8 × 2s failures) → gcp-routes removes the local VIP DNAT
  3. GCP's passthrough-ILB health check only deprograms the backend at T+25–30s (CheckIntervalSec: 5 × UnhealthyThreshold: 6)
  4. In the ~10s gap, the node's own new connections to the api-int VIP are blackholed (a backend VM cannot reach its own VIP through the fabric); reused connections survive via conntrack

Result: 2–12 disruption events per internal-lb new-connections backend per run, in two bursts (the hosting master rolls twice during the ensures external ip policy test). Verified across 7 runs of periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial: disruption occurs iff the replacement replica landed on a master (audit-log source IPs, scheduling events, gcp-routes journals and apiserver-watcher logs all line up; full evidence in the Jira). The previously suspected cluster-kube-apiserver-operator#2247 and openshift/kubernetes#2713 are exonerated — the payload correlation was scheduler-placement luck.

The fix

Pin the internal-lb pollers to worker nodes with a nodeSelector. The master toleration is kept so the pods remain schedulable on compact/SNO clusters where control-plane nodes also carry the worker role.


This PR was generated using AI (Assisted-By: Claude Fable 5). Please verify before acting on it.

Summary by CodeRabbit

  • Bug Fixes
  • Improved internal load-balancer polling reliability by scheduling pollers on worker nodes.
  • Preserved compatibility with compact clusters by allowing scheduling on control-plane nodes when necessary.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot openshift-ci-robot removed the jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. label Jul 31, 2026
@openshift-ci

openshift-ci Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@mkowalski: trigger 5 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial
  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/5700ae40-8cf3-11f1-9454-c054eb1e913c-0

@mkowalski

Copy link
Copy Markdown
Contributor Author

/jira refresh

@openshift-ci-robot

Copy link
Copy Markdown

@mkowalski: This pull request references Jira Issue OCPBUGS-100298, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

/jira refresh

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@mkowalski

Copy link
Copy Markdown
Contributor Author

/payload-abort

@openshift-ci

openshift-ci Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@mkowalski: Failed to abort some payload jobs. Total jobs aborted: 5. Failed jobs: 0aef3897-5627-467b-a723-74a9f004557e

@mkowalski

Copy link
Copy Markdown
Contributor Author

/payload-aggregate periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial 10

@openshift-ci

openshift-ci Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@mkowalski: trigger 1 job(s) for the /payload-(with-prs|job|aggregate|job-with-prs|aggregate-with-prs) command

  • periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial

See details on https://pr-payload-tests.ci.openshift.org/runs/ci/8f55ea30-8cf3-11f1-96f9-913f299bfc92-0

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/dep-internal-lb.yaml`:
- Around line 70-71: Update the deployment strategy associated with the
nodeSelector in the internal load balancer manifest to prevent a single-worker
rollout deadlock: use Recreate, or configure RollingUpdate with maxUnavailable
set to 1 and maxSurge set to 0. Preserve the existing hostname anti-affinity
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: e6cbc6ab-eb42-4756-949e-f7475d3d13af

📥 Commits

Reviewing files that changed from the base of the PR and between 2afc4ee and 286e566.

📒 Files selected for processing (1)
  • pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/dep-internal-lb.yaml

Comment on lines +70 to +71
nodeSelector:
node-role.kubernetes.io/worker: ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
target='pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/dep-internal-lb.yaml'
printf '%s\n' '--- target files ---'
git ls-files 'pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/*'
printf '%s\n' '--- target outline/size ---'
wc -l "$target"
printf '%s\n' '--- target manifest ---'
cat -n "$target"
printf '%s\n' '--- related anti-affinity, strategy, topology, and selector references ---'
rg -n -C 4 'dep-internal-lb|podAntiAffinity|topologyKey|maxUnavailable|maxSurge|node-role\.kubernetes\.io/worker|replicas:' \
  pkg/monitortests/kubeapiserver/disruptioninclusterapiserver \
  pkg/monitortests/kubeapiserver 2>/dev/null | head -n 500

Repository: openshift/origin

Length of output: 34076


🏁 Script executed:

#!/bin/bash
set -eu
target='pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/dep-internal-lb.yaml'
printf '%s\n' '--- target files ---'
git ls-files 'pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/*'
printf '%s\n' '--- target manifest ---'
cat -n "$target"
printf '%s\n' '--- related scheduling references ---'
rg -n -C 4 'dep-internal-lb|podAntiAffinity|topologyKey|maxUnavailable|maxSurge|node-role\.kubernetes\.io/worker|replicas:' \
  pkg/monitortests/kubeapiserver/disruptioninclusterapiserver \
  pkg/monitortests/kubeapiserver 2>/dev/null | head -n 500

Repository: openshift/origin

Length of output: 33917


🏁 Script executed:

#!/bin/bash
set -eu
target='pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/dep-internal-lb.yaml'

python3 - "$target" <<'PY'
import re
import sys
from decimal import Decimal, ROUND_CEILING, ROUND_FLOOR

path = sys.argv[1]
text = open(path, encoding="utf-8").read()

def scalar(pattern, name):
    match = re.search(pattern, text, re.MULTILINE)
    if not match:
        raise SystemExit(f"missing {name}")
    return match.group(1)

replicas = int(scalar(r"^  replicas:\s+(\d+)\s*$", "replicas"))
strategy = re.search(r"^  strategy:\s*$", text, re.MULTILINE)
anti_affinity = bool(re.search(
    r"requiredDuringSchedulingIgnoredDuringExecution:.*?"
    r"topologyKey:\s*[\"']kubernetes\.io/hostname[\"']",
    text, re.DOTALL))
worker_selector = bool(re.search(
    r"^      nodeSelector:\s*\n"
    r"^        node-role\.kubernetes\.io/worker:\s*[\"']?[\"']?\s*$",
    text, re.MULTILINE))

def percent(value, replicas, rounding):
    number = Decimal(value.rstrip("%")) * replicas / Decimal(100)
    return int(number.to_integral_value(rounding=rounding))

# Deployment defaults: maxSurge=25% rounds up; maxUnavailable=25% rounds down.
max_surge = percent("25%", replicas, ROUND_CEILING)
max_unavailable = percent("25%", replicas, ROUND_FLOOR)

print({
    "replicas": replicas,
    "explicit_strategy": bool(strategy),
    "required_hostname_anti_affinity": anti_affinity,
    "worker_selector": worker_selector,
    "default_maxSurge": max_surge,
    "default_maxUnavailable": max_unavailable,
    "single_eligible_node_replacement_pending": (
        replicas == 1 and anti_affinity and worker_selector
        and max_surge >= 1 and max_unavailable == 0
    ),
})

assert replicas == 1
assert not strategy
assert anti_affinity
assert worker_selector
assert max_surge == 1
assert max_unavailable == 0
assert replicas == 1 and anti_affinity and worker_selector
PY

printf '%s\n' '--- target file change relative to parent ---'
git diff --stat HEAD^ -- "$target" 2>/dev/null || true
git diff --unified=3 HEAD^ -- "$target" 2>/dev/null | head -n 160 || true

printf '%s\n' '--- explicit rollout strategies in related manifests ---'
rg -n -C 3 '^[[:space:]]+strategy:|maxUnavailable:|maxSurge:' \
  pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests

Repository: openshift/origin

Length of output: 3849


🏁 Script executed:

#!/bin/bash
set -eu
target='pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/dep-internal-lb.yaml'

python3 - "$target" <<'PY'
import re
import sys
from decimal import Decimal, ROUND_CEILING, ROUND_FLOOR

path = sys.argv[1]
text = open(path, encoding="utf-8").read()

def scalar(pattern, name):
    match = re.search(pattern, text, re.MULTILINE)
    if not match:
        raise SystemExit(f"missing {name}")
    return match.group(1)

replicas = int(scalar(r"^  replicas:\s+(\d+)\s*$", "replicas"))
strategy = re.search(r"^  strategy:\s*$", text, re.MULTILINE)
anti_affinity = bool(re.search(
    r"requiredDuringSchedulingIgnoredDuringExecution:.*?"
    r"topologyKey:\s*[\"']kubernetes\.io/hostname[\"']",
    text, re.DOTALL))
worker_selector = bool(re.search(
    r"^      nodeSelector:\s*\n"
    r"^        node-role\.kubernetes\.io/worker:\s*[\"']?[\"']?\s*$",
    text, re.MULTILINE))

def percent(value, replicas, rounding):
    number = Decimal(value.rstrip("%")) * replicas / Decimal(100)
    return int(number.to_integral_value(rounding=rounding))

max_surge = percent("25%", replicas, ROUND_CEILING)
max_unavailable = percent("25%", replicas, ROUND_FLOOR)

print({
    "replicas": replicas,
    "explicit_strategy": bool(strategy),
    "required_hostname_anti_affinity": anti_affinity,
    "worker_selector": worker_selector,
    "default_maxSurge": max_surge,
    "default_maxUnavailable": max_unavailable,
    "single_eligible_node_replacement_pending": (
        replicas == 1 and anti_affinity and worker_selector
        and max_surge >= 1 and max_unavailable == 0
    ),
})

assert replicas == 1
assert not strategy
assert anti_affinity
assert worker_selector
assert max_surge == 1
assert max_unavailable == 0
PY

printf '%s\n' '--- target file change relative to parent ---'
git diff --stat HEAD^ -- "$target" 2>/dev/null || true
git diff --unified=3 HEAD^ -- "$target" 2>/dev/null | head -n 160 || true

printf '%s\n' '--- explicit rollout strategies in related manifests ---'
rg -n -C 3 '^[[:space:]]+strategy:|maxUnavailable:|maxSurge:' \
  pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests

Repository: openshift/origin

Length of output: 3849


Prevent a single-node rollout deadlock.

If only one worker node is eligible, required hostname anti-affinity blocks the replacement Pod. With one replica, the default RollingUpdate strategy sets maxUnavailable to 0, so the old Pod remains and the rollout stays Pending.

Set maxUnavailable: 1 and maxSurge: 0, use Recreate, or make anti-affinity preferred.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@pkg/monitortests/kubeapiserver/disruptioninclusterapiserver/manifests/dep-internal-lb.yaml`
around lines 70 - 71, Update the deployment strategy associated with the
nodeSelector in the internal load balancer manifest to prevent a single-worker
rollout deadlock: use Recreate, or configure RollingUpdate with maxUnavailable
set to 1 and maxSurge set to 0. Preserve the existing hostname anti-affinity
behavior.

Sources: Coding guidelines, MCP tools

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

@mkowalski

Copy link
Copy Markdown
Contributor Author

Validation results: 10× e2e-gcp-ovn-serial via /payload-aggregate

Ran the job 10 times on this PR (aggregate run) and measured internal-LB disruption from the spyglass interval files, compared against the nightly runs without this PR (Jul 25–29, the period covered by OCPBUGS-100298).

ilb-new = DisruptionBegan events across kube-api-http1/http2-internal-lb-new-connections + openshift-api-http2-internal-lb-new-connections (the regression signature). localhost = localhost-backend events, shown as proof that the same double kube-apiserver rollout occurred in every run.

Without PR — nightly periodic-ci-openshift-release-main-nightly-5.0-e2e-gcp-ovn-serial

Run (UTC) ilb-new localhost poller displaced to master?
Jul 25 18:57 15 48 yes (master)
Jul 26 02:24 27 ~49 yes (master-2)
Jul 26 09:11 19 yes
Jul 26 16:39 0 53 no (replacement → worker-f)
Jul 26 22:34 19 yes (master-0)
Jul 27 05:43 24 yes
Jul 27 11:22 1 49 no (stayed on workers)
Jul 27 17:18 20 yes
Jul 29 00:08 22 yes
Jul 29 06:06 13 yes
Mean / affected 16.0 8/10 runs affected

With PR — 10× openshift-origin-31466-nightly-5.0-e2e-gcp-ovn-serial (aggregation 309ea076…)

Run Job result ilb-new ilb-reused localhost
2083211564936073216 success 0 0 50
2083211565372280832 success 0 0 52
2083211565833654272 success 0 5 58
2083211566274056192 failure* 0 0 48
2083211566731235328 success 0 0 48
2083211567205191680 success 0 0 51
2083211567628816384 failure* 0 0 49
2083211568090189824 failure* 0 0 53
2083211568547368960 failure* 0 0 55
2083211568975187968 failure* 0 1 51
Mean / affected 0.0 0/10 runs affected

* job-level failures are unrelated MonitorTest flakes (e.g. kubelet-container-restarts, KubePodNotReady alert invariants), not disruption.

Mechanism confirmed end-to-end

In run 2083211568975187968 the exact trigger scenario replayed with the fix in place: the NoExecuteTaintManager Multiple Pods test (16:59–17:01) evicted an internal-lb-monitor replica, and the replacement was scheduled to worker-c (nodeSelector now forbids masters) instead of a control-plane node; the external ip policy test then ran its two kube-apiserver rollouts (18:16–18:38) with zero internal-LB new-connections disruption.

Summary: without the PR 8/10 runs showed 13–27 false disruption events (mean 16.0); with the PR 0/10 runs showed any (0.0), while the rollout pattern (localhost events 48–58) was identical.


This comment was generated using AI (Assisted-By: Claude Fable 5). Please verify before acting on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants